import time
import re

import allure
from ddddocr import DdddOcr
from selenium import webdriver
from selenium.common import NoSuchElementException
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support.select import Select


class KeyWord:
    def __init__(self, driver: webdriver.Chrome):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
        self.vars = {}

    def find_element(self, locator_type, locator_value):
        """
        封装元素定位方法，传入定位方式和对应的值，自动使用显示等待，定位到的元素高亮显示
        :param locator_type: 元素定位方式
        :param locator_value: 元素定位值
        :return:
        """
        try:
            by = getattr(By, locator_type.upper())
            element = self.wait.until(lambda _: self.driver.find_element(by, locator_value))
            self.driver.execute_script(
                "arguments[0].setAttribute('style',arguments[1]);",
                element,
                "border: 2px solid red;"
            )
            return element
        except TimeoutError:
            raise NoSuchElementException(f"元素定位失败：定位方式{locator_type}，定位的值{locator_value}")

    @classmethod
    def all_keyword(cls):
        """
        列出所有可用关键字
        :return:
        """
        _all_keyword = []
        for attr in dir(cls):
            if attr.startswith("key_"):
                method = getattr(cls, attr)
                if callable(method):
                    _all_keyword.append(method[4:])
        return _all_keyword

    @allure.step
    def key_open_browser(self, browser):
        """
        关键字：open_browser
        打开浏览器
        :param browser: 浏览器类型
        :return:
        """
        browser = browser.capitalize()
        self.driver = getattr(webdriver, browser)()

    @allure.step
    def key_load_url(self, url):
        """
        关键字：load_url
        加载地址
        :param url: 页面地址
        :return:
        """
        self.driver.get(url)

    @allure.step
    def key_sleep(self, n):
        """
        关键字：sleep
        强制等待
        :param n: 等待时间，单位秒(s)
        :return:
        """
        time.sleep(int(n))

    @allure.step
    def key_click(self, locator_type, locator_value):
        """
        关键字：click
        点击操作
        :param locator_type: 定位方式
        :param locator_value: 定位值
        :return:
        """
        self.find_element(locator_type, locator_value).click()

    @allure.step
    def key_input(self, locator_type, locator_value, txt):
        """
        关键字：input
        输入操作
        :param locator_type: 定位方式
        :param locator_value: 定位值
        :param txt: 需要输入的值
        :return:
        """
        if txt == "None":
            self.find_element(locator_type, locator_value).send_keys("")
        else:
            self.find_element(locator_type, locator_value).send_keys(txt)

    @allure.step
    def key_save_text(self, locator_type, locator_value, var_name, not_expect_empty=False):
        """
        关键字：save_text
        保存获取到的文本值
        :param var_name: 要保存的变量名
        :param locator_type: 要保存文本的定位方式
        :param locator_value: 要保存文本的定位值
        :param not_expect_empty: 不期望空值
        :return:
        """
        if not_expect_empty:
            # 不期望空，等待元素有text值
            text = self.wait.until(lambda _: self.find_element(locator_type, locator_value).text)
        else:
            text = self.find_element(locator_type, locator_value).text
        self.vars[var_name] = text

    @allure.step
    def key_save_url(self, var_name):
        """
        关键字：save_url
        保存当前页面url
        :param var_name: 要保存的变量
        :return:
        """
        self.vars[var_name] = {self.driver.current_url}

    @allure.step
    def key_save_title(self, var_name):
        """
        关键字：save_title
        保存页面标题
        :param var_name:要保存的变量名
        :return:
        """
        self.vars[var_name] = self.driver.title

    @allure.step
    def key_save_alert_text(self, var_name):
        """
        关键字：save_alert_text
        保存弹窗的文本
        :param var_name: 要保存的变量名
        :return:
        """
        text = self.wait.until(ec.alert_is_present()).text
        self.vars[var_name] = text

    @allure.step
    def key_alert_click(self, action="accept"):
        """
        关键字：alert_click
        弹窗点击操作
        :param action: 要做的操作，确定(accept)、取消(dismiss),默认accept
        :return:
        """
        alert = self.wait.until(ec.alert_is_present())
        if action.lower() == "dismiss":
            alert.dismiss()
        else:
            alert.accept()

    @allure.step
    def key_alert_input(self, txt):
        """
        关键字：alert_input
        弹窗输入操作
        :param txt: 要输入的文本
        :return:
        """
        self.wait.until(ec.alert_is_present()).send_keys(txt)

    @allure.step
    def key_choice(self, locator_type, locator_value, txt):
        """
        关键字：choice
        下拉框选择，选择单个
        :param locator_type: 定位方式
        :param locator_value: 定位值
        :param txt: 要选择的值
        :return:
        """
        sel = self.find_element(locator_type, locator_value)
        Select(sel).select_by_visible_text(txt)

    @allure.step
    def key_enter_frame(self, locator_type, locator_value):
        """
        关键字：frame_enter
        进入iframe框架
        :param locator_type: iframe框架定位方式
        :param locator_value: iframe框架定位值
        :return:
        """
        self.driver.switch_to.frame(self.find_element(locator_type, locator_value))

    @allure.step
    def key_exit_frame(self, to_parent=False):
        """
        关键字：exit_frame
        退出iframe框架
        :param to_parent: 多层iframe时，传入True，返回到上一级iframe内
        :return:
        """
        if to_parent:
            self.driver.switch_to.parent_frame()
        else:
            self.driver.switch_to.default_content()

    @allure.step
    def key_change_window(self, title_name, partial_match=True):
        """
        关键字：change_window
        根据窗口标题切换窗口
        :param title_name: 窗口标题
        :param partial_match: 是否部分匹配，默认允许部分匹配
        :return:
        """
        all_handles = self.driver.window_handles
        for handle in all_handles:
            self.driver.switch_to.window(handle)
            current_title = self.driver.title
            if (partial_match and title_name in current_title) or (not partial_match and title_name == current_title):
                break

    @allure.step
    def key_scroll(self, locator_type, locator_value):
        """
        关键字：scroll
        滚动到定位的元素
        :param locator_type: 定位方式
        :param locator_value: 定位值
        :return:
        """
        element = self.driver.find_element(locator_type, locator_value)
        ActionChains(self.driver).scroll_to_element(element).perform()

    @allure.step
    def key_move(self, locator_type, locator_value):
        """
        关键字：move
        移动到元素
        :param locator_type: 定位方式
        :param locator_value: 定位值
        :return:
        """
        element = self.driver.find_element(locator_type, locator_value)
        ActionChains(self.driver).move_to_element(element).perform()

    @allure.step
    def key_upload_file(self, locator_type, locator_value, file_path):
        """
        关键字方法：upload_file
        适用于非input标签的文件上传
        :param locator_type: 定位方式
        :param locator_value: 定位值
        :param file_path: 文件路径
        :return:
        """
        self.key_click(locator_type, locator_value)
        input_element = self.driver.find_element(By.XPATH, "//input[@id='file']")
        self.driver.execute_script("arguments[0].style.display = 'block';", input_element)
        input_element.send_keys(file_path)

    @allure.step
    def key_login(self, username, password):
        """
        关键字：login
        登录操作
        :param username: 用户名
        :param password: 密码
        :return:
        """
        self.key_load_url("http://novel.hctestedu.com/user/login.html")
        self.key_input("id", "txtUName", username)
        self.key_input("id", "txtPassword", password)
        self.key_click("id", "btnLogin")


    @allure.step
    def key_code_parse(self, locator_type, locator_value, var_name):
        """
        关键字：code_parse
        验证码识别
        :param locator_type:
        :param locator_value:
        :param var_name:
        :return:
        """
        image_data = self.find_element(locator_type, locator_value).screenshot_as_png
        verification_code = DdddOcr().classification(image_data)
        self.vars[var_name] = verification_code


class Validator:
    def __init__(self, expected_value, assert_name, actual_value):
        """预期结果和实际结果进行比较"""
        self.expected_value = expected_value
        self.assert_name = assert_name
        self.actual_value = actual_value

    def is_valid(self):
        """
        执行断言，返回布尔值表示断言是否成功
        :return: True表示断言成功，False表示断言失败
        """
        assert_method = getattr(self, f"assert_{self.assert_name}")
        assert_method(self.expected_value, self.actual_value)
        return True

    @allure.step
    def assert_equal(self, expected_value, actual_value):
        """
        断言方法：equal
        验证实际值等于期望值
        :param expected_value:期望值
        :param actual_value: 实际值
        :return:
        """
        assert actual_value == expected_value, f"断言失败：期望{expected_value},实际{actual_value}"

    @allure.step
    def assert_not_equal(self, expected_value, actual_value):
        """
        断言方法：not_equal
        验证实际值不等于期望值
        :param expected_value: 期望值
        :param actual_value: 实际值
        :return:
        """
        assert actual_value != expected_value, f"断言失败：期望{expected_value},实际{actual_value}"

    @allure.step
    def assert_contains(self, expected_value, actual_value):
        """
        断言方法：contains
        验证实际值包含期望值
        :param expected_value:期望值
        :param actual_value:实际值
        :return:
        """
        assert expected_value in actual_value, f"断言失败：期望{expected_value},实际{actual_value}"

    @allure.step
    def assert_not_contains(self, expected_value, actual_value):
        """
        断言方法：not_contains
        :param expected_value: 期望值
        :param actual_value: 预期值
        :return:
        """
        assert expected_value not in actual_value, f"断言失败：期望{expected_value},实际{actual_value}"

    @allure.step
    def assert_greater(self, expected_value, actual_value):
        """
        断言方法：greater
        :param expected_value: 期望值
        :param actual_value: 实际值
        :return:
        """
        assert actual_value > expected_value, f"断言失败：期望{expected_value},实际{actual_value}"

    @allure.step
    def assert_greater_equal(self, expected_value, actual_value):
        """
        断言方法：greater_equal
        :param expected_value: 期望值
        :param actual_value: 实际值
        :return:
        """
        assert actual_value >= expected_value, f"断言失败：期望{expected_value},实际{actual_value}"

    @allure.step
    def assert_less(self, expected_value, actual_value):
        """
        断言方法：less
        :param expected_value: 期望值
        :param actual_value: 实际值
        :return:
        """
        assert actual_value < expected_value, f"断言失败：期望{expected_value},实际{actual_value}"

    def assert_less_equal(self, expected_value, actual_value):
        """
        断言方法：less_equal
        :param expected_value: 期望值
        :param actual_value: 实际值
        :return:
        """
        assert actual_value <= expected_value, f"断言失败：期望{expected_value},实际{actual_value}"

    @allure.step
    def assert_regex(self, expected_value, actual_value):
        """
        断言方法：regex
        验证实际值匹配正则表达式
        :param expected_value: 正则表达式
        :param actual_value: 实际值
        :return:
        """
        assert re.search(expected_value, actual_value), f"断言失败：期望匹配的正则{expected_value},实际{actual_value}"
